﻿using System;
using UnityEngine;

namespace JH
{
    /// <summary>
    ///  数值型缓动函数
    /// </summary>
    public static class EaseFunctionNumeric
    {
        /// <summary>
        ///  获取缓动值
        /// </summary>
        /// <param name="start">起始值</param>
        /// <param name="end">结束值</param>
        /// <param name="ease">缓动类型</param>
        /// <param name="f">0~1之间的进度</param>
        /// <typeparam name="T">数据类型</typeparam>
        /// <returns>当进度为f时的数据值</returns>
        public static T Tween<T>(T start, T end, Ease ease, float f)
        {
            if (start == null || end == null) throw new ArgumentNullException();
            if (f < 0 || f > 1) throw new ArgumentOutOfRangeException();

            //如果可以转换为double类型,直接转换并利用double类型进行计算
            if (start is IConvertible)
            {
                double s = Convert.ToDouble(start);
                double e = Convert.ToDouble(end);
                double v = Tween(s, e, ease, f);
                return (T)Convert.ChangeType(v, typeof(T));
            }

            // 如果是Vector2类型，直接调用Vector2的缓动函数
            if (start is Vector2)
            {
                return (T)(object)Tween((Vector2)(object)start, (Vector2)(object)end, ease, f);
            }

            // 如果是Vector3类型，直接调用Vector3的缓动函数
            if (start is Vector3)
            {
                return (T)(object)Tween((Vector3)(object)start, (Vector3)(object)end, ease, f);
            }

            // 如果是Vector4类型，直接调用Vector4的缓动函数
            if (start is Vector4)
            {
                return (T)(object)Tween((Vector4)(object)start, (Vector4)(object)end, ease, f);
            }

            // 如果是Color类型，直接调用Color的缓动函数
            if (start is Color)
            {
                return (T)(object)Tween((Color)(object)start, (Color)(object)end, ease, f);
            }

            // 如果实现了ITweenData接口，直接调用接口方法
            if (start is ITweenData<T> tweenData)
            {
                return tweenData.Tween(start, end, ease, f);
            }

            // 抛出尚不支持，异常
            throw new NotSupportedException("暂不支持该类型" + typeof(T));
        }


        /// <summary>
        ///  获取Vector2的缓动值
        /// </summary>
        /// <inheritdoc cref="Tween{T}"/>
        public static Vector2 Tween(Vector2 start, Vector2 end, Ease ease, float f)
        {
            return new Vector2(Tween(start.x, end.x, ease, f),
                Tween(start.y, end.y, ease, f));
        }

        /// <summary>
        ///  获取Vector3的缓动值
        /// </summary>
        /// <inheritdoc cref="Tween{T}"/>
        public static Vector3 Tween(Vector3 start, Vector3 end, Ease ease, float f)
        {
            return new Vector3(Tween(start.x, end.x, ease, f),
                Tween(start.y, end.y, ease, f),
                Tween(start.z, end.z, ease, f));
        }

        /// <summary>
        ///  获取Vector4的缓动值
        /// </summary>
        /// <inheritdoc cref="Tween{T}"/>
        public static Vector4 Tween(Vector4 start, Vector4 end, Ease ease, float f)
        {
            return new Vector4(Tween(start.x, end.x, ease, f),
                Tween(start.y, end.y, ease, f),
                Tween(start.z, end.z, ease, f),
                Tween(start.w, end.w, ease, f));
        }

        /// <summary>
        ///  获取Color的缓动值
        /// </summary>
        ///  <inheritdoc cref="Tween{T}"/>
        public static Color Tween(Color start, Color end, Ease ease, float f)
        {
            return new Color(Tween(start.r, end.r, ease, f),
                Tween(start.g, end.g, ease, f),
                Tween(start.b, end.b, ease, f),
                Tween(start.a, end.a, ease, f));
        }

        /// <summary>
        ///  获取单精度浮点型的缓动值
        /// </summary>
        /// <inheritdoc cref="Tween{T}"/>
        public static double Tween(double start, double end, Ease ease, float f)
        {
            // 计算差值
            double difference = end - start;

            // 根据不同的缓动类型计算缓动值
            double t = ease switch
            {
                Ease.Linear => f,

                // Sine
                Ease.InSine => 1 - Math.Cos(f * Math.PI / 2),
                Ease.OutSine => Math.Sin(f * Math.PI / 2),
                Ease.InOutSine => -(Math.Cos(Math.PI * f) - 1) / 2,

                // Quad
                Ease.InQuad => f * f,
                Ease.OutQuad => 1 - (1 - f) * (1 - f),
                Ease.InOutQuad => f < 0.5 ? 2 * f * f : 1 - Math.Pow(-2 * f + 2, 2) / 2,

                // Cubic
                Ease.InCubic => f * f * f,
                Ease.OutCubic => 1 - Math.Pow(1 - f, 3),
                Ease.InOutCubic => f < 0.5 ? 4 * f * f * f : 1 - Math.Pow(-2 * f + 2, 3) / 2,

                // Quart
                Ease.InQuart => f * f * f * f,
                Ease.OutQuart => 1 - Math.Pow(1 - f, 4),
                Ease.InOutQuart => f < 0.5 ? 8 * f * f * f * f : 1 - Math.Pow(-2 * f + 2, 4) / 2,

                // Quint
                Ease.InQuint => f * f * f * f * f,
                Ease.OutQuint => 1 - Math.Pow(1 - f, 5),
                Ease.InOutQuint => f < 0.5 ? 16 * f * f * f * f * f : 1 - Math.Pow(-2 * f + 2, 5) / 2,

                // Expo
                Ease.InExpo => Mathf.Approximately(f, 0) ? 0 : Math.Pow(2, 10 * f - 10),
                Ease.OutExpo => Mathf.Approximately(f, 1) ? 1 : 1 - Math.Pow(2, -10 * f),
                Ease.InOutExpo => Mathf.Approximately(f, 0) ? 0 :
                    Mathf.Approximately(f, 1) ? 1 :
                    f < 0.5 ? Math.Pow(2, 20 * f - 10) / 2 : (2 - Math.Pow(2, -20 * f + 10)) / 2,

                // Circ
                Ease.InCirc => 1 - Math.Sqrt(1 - Math.Pow(f, 2)),
                Ease.OutCirc => Math.Sqrt(1 - Math.Pow(f - 1, 2)),
                Ease.InOutCirc => f < 0.5
                    ? (1 - Math.Sqrt(1 - Math.Pow(2 * f, 2))) / 2
                    : (Math.Sqrt(1 - Math.Pow(-2 * f + 2, 2)) + 1) / 2,

                // Back
                Ease.InBack => 2.70158 * f * f * f - 1.70158 * f * f,
                Ease.OutBack => 1 + 2.70158 * Math.Pow(f - 1, 3) + 1.70158 * Math.Pow(f - 1, 2),
                Ease.InOutBack => f < 0.5
                    ? Math.Pow(2 * f, 2) * ((2.5949095 + 1) * 2 * f - 2.5949095) / 2
                    : (Math.Pow(2 * f - 2, 2) * ((2.5949095 + 1) * (f * 2 - 2) + 2.5949095) + 2) / 2,

                // Elastic
                Ease.InElastic => Mathf.Approximately(f, 0) ? 0 :
                    Mathf.Approximately(f, 1) ? 1 :
                    -Math.Pow(2, 10 * f - 10) * Math.Sin((f * 10 - 10.75) * (2 * Math.PI / 3)),

                // OutElastic
                Ease.OutElastic => Mathf.Approximately(f, 0) ? 0 :
                    Mathf.Approximately(f, 1) ? 1 :
                    Math.Pow(2, -10 * f) * Math.Sin((f * 10 - 0.75) * (2 * Math.PI / 3)) + 1,

                // InOutElastic
                Ease.InOutElastic => Mathf.Approximately(f, 0) ? 0 :
                    Mathf.Approximately(f, 1) ? 1 :
                    f < 0.5 ? -(Math.Pow(2, 20 * f - 10) * Math.Sin((20 * f - 11.125) * (2 * Math.PI / 4.5))) / 2 :
                    Math.Pow(2, -20 * f + 10) * Math.Sin((20 * f - 11.125) * (2 * Math.PI / 4.5)) / 2 + 1,

                // Bounce
                Ease.InBounce => 1 - GetOutBounce(1 - f),
                Ease.OutBounce => GetOutBounce(f),
                Ease.InOutBounce => f < 0.5
                    ? (1 - GetOutBounce(1 - 2 * f)) / 2
                    : (1 + GetOutBounce(2 * f - 1)) / 2,

                _ => f // 默认返回线性插值
            };

            // 返回起始值加上差值乘以缓动值
            return start + difference * t;
        }

        /// <summary>
        /// 辅助函数：计算OutBounce
        /// </summary>
        private static double GetOutBounce(double x)
        {
            const double n1 = 7.5625;
            const double d1 = 2.75;

            if (x < 1 / d1)
            {
                return n1 * x * x;
            }

            if (x < 2 / d1)
            {
                x -= 1.5 / d1;
                return n1 * x * x + 0.75;
            }

            if (x < 2.5 / d1)
            {
                x -= 2.25 / d1;
                return n1 * x * x + 0.9375;
            }

            x -= 2.625 / d1;
            return n1 * x * x + 0.984375;
        }
    }
}